package com.google.gwt.user.rebind.rpc;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JPackage;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.generator.GenUtil;
import com.google.gwt.dev.generator.NameFactory;
import com.google.gwt.dev.util.Util;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.impl.ClientSerializationStreamWriter;
import com.google.gwt.user.client.rpc.impl.FailedRequest;
import com.google.gwt.user.client.rpc.impl.FailingRequestBuilder;
import com.google.gwt.user.client.rpc.impl.RemoteServiceProxy;
import com.google.gwt.user.client.rpc.impl.RequestCallbackAdapter.ResponseReader;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.user.server.rpc.SerializationPolicyLoader;
import com.seventhdawn.gwt.rcx.client.annotation.ClientProxySuperclass;
import com.seventhdawn.gwt.rcx.client.annotation.CustomSerializableRoots;

/**
 * Creates an asynchronous client-side proxy implementation of a
 * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService} interface
 * as well as the necessary type and field serializers.
 * <p>
 * This class overrides the behavior of certain {@link ProxyCreator} methods:
 * <ul>
 * <li>{@link #getSourceWriter(TreeLogger, GeneratorContext, JClassType)
 * getSourceWriter} regarding the
 * {@link com.seventhdawn.gwt.rcx.client.annotation.ClientProxySuperclass
 * ClientProxySuperclass} annotation
 * <li>
 * {@link #addRemoteServiceRootTypes(TreeLogger, TypeOracle, SerializableTypeOracleBuilder, JClassType)
 * addRemoteServiceRootTypes} regarding the
 * {@link com.seventhdawn.gwt.rcx.client.annotation.CustomSerializableRoots
 * CustomSerializableRoots} annotation
 * </ul>
 * Most of the source code is taken unchanged from {@link ProxyCreator} since
 * it was apparently not designed for direct subclassing. 
 * 
 * @see UberServiceInterfaceProxyGenerator
 * @author various
 */
public class UberProxyCreator {
    
    protected Class<? extends RemoteServiceProxy> getProxySuperclass(TreeLogger logger) {
        ClientProxySuperclass rpcSuperAnnotation = serviceIntf
                .getAnnotation(ClientProxySuperclass.class);
        
        if (rpcSuperAnnotation != null) {
            return rpcSuperAnnotation.value();
        } else {
            logger.log(TreeLogger.INFO, "RemoteService proxy superclass " +
                    "was not defined via ClientProxySuperclass annotation, " +
                    "falling back to default proxy implementation: " +
                    RemoteServiceProxy.class.getSimpleName(), null);
            return RemoteServiceProxy.class;
        }
    }
    
    protected SourceWriter getSourceWriter(TreeLogger logger,
            GeneratorContext ctx, JClassType serviceAsync) {
        JPackage serviceIntfPkg = serviceAsync.getPackage();
        String packageName = serviceIntfPkg == null ? "" : serviceIntfPkg.getName();
        
        PrintWriter printWriter = ctx.tryCreate(logger, packageName, getProxySimpleName());
        if (printWriter == null) {
            return null;
        }
        
        ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(
                packageName, getProxySimpleName());
        
        String[] imports = new String[] {
                ClientSerializationStreamWriter.class.getCanonicalName(),
                GWT.class.getCanonicalName(),
                ResponseReader.class.getCanonicalName(),
                SerializationException.class.getCanonicalName() };
        for (String imp : imports) {
            composerFactory.addImport(imp);
        }
        
        // set client-side proxy superclass
        TreeLogger rpcSuperclassLogger = logger.branch(TreeLogger.DEBUG,
                "Retrieving proxy superclass for service interface " +
                serviceIntf.getQualifiedSourceName(), null);
        Class<? extends RemoteServiceProxy> rpcSuperclass = getProxySuperclass(rpcSuperclassLogger);
        composerFactory.setSuperclass(rpcSuperclass.getSimpleName());
        composerFactory.addImport(rpcSuperclass.getCanonicalName());
        
        composerFactory.addImplementedInterface(serviceAsync.getErasedType()
                .getQualifiedSourceName());
        
        return composerFactory.createSourceWriter(ctx, printWriter);
    }
    
    protected static void addRemoteServiceRootTypes(TreeLogger logger,
            TypeOracle typeOracle, SerializableTypeOracleBuilder stob,
            JClassType remoteService) throws NotFoundException {
        logger = logger.branch(TreeLogger.DEBUG, "Analyzing '"
                + remoteService.getParameterizedQualifiedSourceName()
                + "' for serializable types", null);
        
        JMethod[] methods = remoteService.getOverridableMethods();
        
        JClassType exceptionClass = typeOracle.getType(Exception.class
                .getName());
        
        TreeLogger validationLogger = logger.branch(TreeLogger.DEBUG,
                "Analyzing methods:", null);
        for (JMethod method : methods) {
            TreeLogger methodLogger = validationLogger.branch(TreeLogger.DEBUG,
                    method.toString(), null);
            JType returnType = method.getReturnType();
            if (returnType != JPrimitiveType.VOID) {
                TreeLogger returnTypeLogger = methodLogger.branch(
                        TreeLogger.DEBUG, "Return type: " +
                        returnType.getParameterizedQualifiedSourceName(),
                        null);
                stob.addRootType(returnTypeLogger, returnType);
            }
            
            JParameter[] params = method.getParameters();
            for (JParameter param : params) {
                TreeLogger paramLogger = methodLogger.branch(TreeLogger.DEBUG,
                        "Parameter: " + param.toString(), null);
                JType paramType = param.getType();
                stob.addRootType(paramLogger, paramType);
            }
            
            JType[] exs = method.getThrows();
            if (exs.length > 0) {
                TreeLogger throwsLogger = methodLogger.branch(TreeLogger.DEBUG,
                        "Throws:", null);
                for (JType ex : exs) {
                    if (!exceptionClass.isAssignableFrom(ex.isClass())) {
                        throwsLogger = throwsLogger.branch(
                                TreeLogger.WARN, "'"
                                + ex.getQualifiedSourceName()
                                + "' is not a checked exception; only checked exceptions may be used",
                                null);
                    }
                    
                    stob.addRootType(throwsLogger, ex);
                }
            }
        }
        
        // add custom serializable roots
        TreeLogger customRootAnalysisLogger = logger.branch(TreeLogger.DEBUG,
                "Analyzing custom serializable roots:", null);
        
        CustomSerializableRoots customRootAnnotation = remoteService
                .getAnnotation(CustomSerializableRoots.class);
        
        if (customRootAnnotation != null) {
            Class<? extends IsSerializable>[] customRoots = customRootAnnotation.value();
            
            for (Class<? extends IsSerializable> root : customRoots) {
                TreeLogger customRootLogger = customRootAnalysisLogger.branch(
                        TreeLogger.DEBUG, "Custom root: " + root.getSimpleName(), null);
                JType rootType = typeOracle.getType(root.getName());
                stob.addRootType(customRootLogger, rootType);
            }
        }
    }
    
    
    // following code is taken from ProxyCreator
    
    private static final String ENTRY_POINT_TAG = "gwt.defaultEntryPoint";
    
    private static final Map<JPrimitiveType, ResponseReader> JPRIMITIVETYPE_TO_RESPONSEREADER = new HashMap<JPrimitiveType, ResponseReader>();
    
    private static final String PROXY_SUFFIX = "_Proxy";
    
    /**
     * Add the implicit root types that are needed to make RPC work. These would
     * be {@link String} and {@link IncompatibleRemoteServiceException}.
     */
    private static void addRequiredRoots(TreeLogger logger,
            TypeOracle typeOracle, SerializableTypeOracleBuilder stob)
            throws NotFoundException {
        logger = logger.branch(TreeLogger.DEBUG, "Analyzing implicit types");
        
        // String is always instantiable.
        JClassType stringType = typeOracle.getType(String.class.getName());
        stob.addRootType(logger, stringType);
        
        // IncompatibleRemoteServiceException is always serializable
        JClassType icseType = typeOracle
                .getType(IncompatibleRemoteServiceException.class.getName());
        stob.addRootType(logger, icseType);
    }
    
    private JClassType serviceIntf;
    
    {
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.BOOLEAN,
                ResponseReader.BOOLEAN);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.BYTE,
                ResponseReader.BYTE);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.CHAR,
                ResponseReader.CHAR);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.DOUBLE,
                ResponseReader.DOUBLE);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.FLOAT,
                ResponseReader.FLOAT);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.INT,
                ResponseReader.INT);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.LONG,
                ResponseReader.LONG);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.SHORT,
                ResponseReader.SHORT);
        JPRIMITIVETYPE_TO_RESPONSEREADER.put(JPrimitiveType.VOID,
                ResponseReader.VOID);
    }
    
    public UberProxyCreator(JClassType serviceIntf) {
        assert (serviceIntf.isInterface() != null);
        this.serviceIntf = serviceIntf;
    }
    
    /**
     * Creates the client-side proxy class.
     * 
     * @throws UnableToCompleteException
     */
    public String create(TreeLogger logger, GeneratorContext context)
            throws UnableToCompleteException {
        TypeOracle typeOracle = context.getTypeOracle();
        
        TreeLogger javadocAnnotationDeprecationBranch = null;
        if (GenUtil.warnAboutMetadata()) {
            javadocAnnotationDeprecationBranch = logger.branch(
                    TreeLogger.TRACE,
                    "Scanning this RemoteService for deprecated annotations; "
                            + "Please see "
                            + RemoteServiceRelativePath.class.getName()
                            + " for more information.", null);
        }
        
        JClassType serviceAsync = typeOracle.findType(serviceIntf
                .getQualifiedSourceName()
                + "Async");
        if (serviceAsync == null) {
            logger.branch(TreeLogger.ERROR,
                    "Could not find an asynchronous version for the service interface "
                            + serviceIntf.getQualifiedSourceName(), null);
            RemoteServiceAsyncValidator.logValidAsyncInterfaceDeclaration(
                    logger, serviceIntf);
            throw new UnableToCompleteException();
        }
        
        SourceWriter srcWriter = getSourceWriter(logger, context, serviceAsync);
        if (srcWriter == null) {
            return getProxyQualifiedName();
        }
        
        // Make sure that the async and synchronous versions of the
        // RemoteService
        // agree with one another
        //
        RemoteServiceAsyncValidator rsav = new RemoteServiceAsyncValidator(
                logger, typeOracle);
        Map<JMethod, JMethod> syncMethToAsyncMethMap = rsav.validate(logger,
                serviceIntf, serviceAsync);
        
        // Determine the set of serializable types
        SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
                logger, context.getPropertyOracle(), typeOracle);
        try {
            addRequiredRoots(logger, typeOracle, stob);
            
            addRemoteServiceRootTypes(logger, typeOracle, stob, serviceIntf);
        } catch (NotFoundException e) {
            logger.log(TreeLogger.ERROR, "", e);
            throw new UnableToCompleteException();
        }
        
        // Create a resource file to receive all of the serialization
        // information
        // computed by STOB and mark it as private so it does not end up in the
        // output.
        OutputStream pathInfo = context.tryCreateResource(logger, serviceIntf
                .getQualifiedSourceName()
                + ".rpc.log");
        stob.setLogOutputStream(pathInfo);
        SerializableTypeOracle sto = stob.build(logger);
        if (pathInfo != null) {
            context.commitResource(logger, pathInfo).setPrivate(true);
        }
        
        TypeSerializerCreator tsc = new TypeSerializerCreator(logger, sto,
                context, sto.getTypeSerializerQualifiedName(serviceIntf));
        tsc.realize(logger);
        
        String serializationPolicyStrongName = writeSerializationPolicyFile(
                logger, context, sto);
        
        generateProxyFields(srcWriter, sto, serializationPolicyStrongName);
        
        generateProxyContructor(javadocAnnotationDeprecationBranch, srcWriter);
        
        generateProxyMethods(srcWriter, sto, syncMethToAsyncMethMap);
        
        srcWriter.commit(logger);
        
        return getProxyQualifiedName();
    }
    
    /**
     * Generate the proxy constructor and delegate to the superclass constructor
     * using the default address for the
     * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService}.
     */
    private void generateProxyContructor(
            TreeLogger javadocAnnotationDeprecationBranch,
            SourceWriter srcWriter) {
        srcWriter.println("public " + getProxySimpleName() + "() {");
        srcWriter.indent();
        srcWriter.println("super(GWT.getModuleBaseURL(),");
        srcWriter.indent();
        srcWriter
                .println(getRemoteServiceRelativePath(javadocAnnotationDeprecationBranch)
                        + ", ");
        srcWriter.println("SERIALIZATION_POLICY, ");
        srcWriter.println("SERIALIZER);");
        srcWriter.outdent();
        srcWriter.outdent();
        srcWriter.println("}");
    }
    
    /**
     * Generate any fields required by the proxy.
     */
    private void generateProxyFields(SourceWriter srcWriter,
            SerializableTypeOracle serializableTypeOracle,
            String serializationPolicyStrongName) {
        // Initialize a field with binary name of the remote service interface
        srcWriter
                .println("private static final String REMOTE_SERVICE_INTERFACE_NAME = \""
                        + serializableTypeOracle
                                .getSerializedTypeName(serviceIntf) + "\";");
        srcWriter
                .println("private static final String SERIALIZATION_POLICY =\""
                        + serializationPolicyStrongName + "\";");
        String typeSerializerName = serializableTypeOracle
                .getTypeSerializerQualifiedName(serviceIntf);
        srcWriter.println("private static final " + typeSerializerName
                + " SERIALIZER = new " + typeSerializerName + "();");
        srcWriter.println();
    }
    
    /**
     * Generates the client's asynchronous proxy method.
     */
    private void generateProxyMethod(SourceWriter w,
            SerializableTypeOracle serializableTypeOracle, JMethod syncMethod,
            JMethod asyncMethod) {
        
        w.println();
        
        // Write the method signature
        JType asyncReturnType = asyncMethod.getReturnType().getErasedType();
        w.print("public ");
        w.print(asyncReturnType.getQualifiedSourceName());
        w.print(" ");
        w.print(asyncMethod.getName() + "(");
        
        boolean needsComma = false;
        boolean needsTryCatchBlock = false;
        NameFactory nameFactory = new NameFactory();
        JParameter[] asyncParams = asyncMethod.getParameters();
        for (int i = 0; i < asyncParams.length; ++i) {
            JParameter param = asyncParams[i];
            
            if (needsComma) {
                w.print(", ");
            } else {
                needsComma = true;
            }
            
            /*
             * Ignoring the AsyncCallback parameter, if any method requires a
             * call to SerializationStreamWriter.writeObject we need a try catch
             * block
             */
            JType paramType = param.getType();
            paramType = paramType.getErasedType();
            if (i < asyncParams.length - 1
                    && paramType.isPrimitive() == null
                    && !paramType.getQualifiedSourceName().equals(
                            String.class.getCanonicalName())) {
                needsTryCatchBlock = true;
            }
            
            w.print(paramType.getQualifiedSourceName());
            w.print(" ");
            
            String paramName = param.getName();
            nameFactory.addName(paramName);
            w.print(paramName);
        }
        
        w.println(") {");
        w.indent();
        
        String requestIdName = nameFactory.createName("requestId");
        w.println("int " + requestIdName + " = getNextRequestId();");
        
        String statsMethodExpr = getProxySimpleName() + "."
                + syncMethod.getName();
        String tossName = nameFactory.createName("toss");
        w.println("boolean " + tossName + " = isStatsAvailable() && stats("
                + "timeStat(\"" + statsMethodExpr
                + "\", getRequestId(), \"begin\"));");
        
        w.print(ClientSerializationStreamWriter.class.getSimpleName());
        w.print(" ");
        String streamWriterName = nameFactory.createName("streamWriter");
        w.println(streamWriterName + " = createStreamWriter();");
        w.println("// createStreamWriter() prepared the stream");
        w.println(streamWriterName
                + ".writeString(REMOTE_SERVICE_INTERFACE_NAME);");
        if (needsTryCatchBlock) {
            w.println("try {");
            w.indent();
        }
        
        // Write the method name
        w.println(streamWriterName + ".writeString(\"" + syncMethod.getName()
                + "\");");
        
        // Write the parameter count followed by the parameter values
        JParameter[] syncParams = syncMethod.getParameters();
        w.println(streamWriterName + ".writeInt(" + syncParams.length + ");");
        for (JParameter param : syncParams) {
            w.println(streamWriterName
                    + ".writeString(\""
                    + serializableTypeOracle.getSerializedTypeName(param
                            .getType().getErasedType()) + "\");");
        }
        
        // Encode all of the arguments to the asynchronous method, but exclude
        // the
        // last argument which is the callback instance.
        //
        for (int i = 0; i < asyncParams.length - 1; ++i) {
            JParameter asyncParam = asyncParams[i];
            w.print(streamWriterName + ".");
            w.print(Shared.getStreamWriteMethodNameFor(asyncParam.getType()));
            w.println("(" + asyncParam.getName() + ");");
        }
        
        String payloadName = nameFactory.createName("payload");
        w.println("String " + payloadName + " = " + streamWriterName
                + ".toString();");
        
        w.println(tossName + " = isStatsAvailable() && stats(" + "timeStat(\""
                + statsMethodExpr
                + "\", getRequestId(), \"requestSerialized\"));");
        
        /*
         * Depending on the return type for the async method, return a
         * RequestBuilder, a Request, or nothing at all.
         */
        if (asyncReturnType == JPrimitiveType.VOID) {
            w.print("doInvoke(");
        } else if (asyncReturnType.getQualifiedSourceName().equals(
                RequestBuilder.class.getName())) {
            w.print("return doPrepareRequestBuilder(");
        } else if (asyncReturnType.getQualifiedSourceName().equals(
                Request.class.getName())) {
            w.print("return doInvoke(");
        } else {
            // This method should have been caught by
            // RemoteServiceAsyncValidator
            throw new RuntimeException("Unhandled return type "
                    + asyncReturnType.getQualifiedSourceName());
        }
        
        JParameter callbackParam = asyncParams[asyncParams.length - 1];
        String callbackName = callbackParam.getName();
        JType returnType = syncMethod.getReturnType();
        w.print("ResponseReader." + getResponseReaderFor(returnType).name());
        w.println(", \"" + getProxySimpleName() + "." + syncMethod.getName()
                + "\", getRequestId(), " + payloadName + ", " + callbackName
                + ");");
        
        if (needsTryCatchBlock) {
            w.outdent();
            w.print("} catch (SerializationException ");
            String exceptionName = nameFactory.createName("ex");
            w.println(exceptionName + ") {");
            w.indent();
            if (!asyncReturnType.getQualifiedSourceName().equals(
                    RequestBuilder.class.getName())) {
                /*
                 * If the method returns void or Request, signal the
                 * serialization error immediately. If the method returns
                 * RequestBuilder, the error will be signaled whenever
                 * RequestBuilder.send() is invoked.
                 */
                w.println(callbackName + ".onFailure(" + exceptionName + ");");
            }
            if (asyncReturnType.getQualifiedSourceName().equals(
                    RequestBuilder.class.getName())) {
                w.println("return new " + FailingRequestBuilder.class.getName()
                        + "(" + exceptionName + ", " + callbackName + ");");
            } else if (asyncReturnType.getQualifiedSourceName().equals(
                    Request.class.getName())) {
                w
                        .println("return new " + FailedRequest.class.getName()
                                + "();");
            } else {
                assert asyncReturnType == JPrimitiveType.VOID;
            }
            w.outdent();
            w.println("}");
        }
        w.outdent();
        w.println("}");
    }
    
    private void generateProxyMethods(SourceWriter w,
            SerializableTypeOracle serializableTypeOracle,
            Map<JMethod, JMethod> syncMethToAsyncMethMap) {
        JMethod[] syncMethods = serviceIntf.getOverridableMethods();
        for (JMethod syncMethod : syncMethods) {
            
            JMethod asyncMethod = syncMethToAsyncMethMap.get(syncMethod);
            assert (asyncMethod != null);
            
            JClassType enclosingType = syncMethod.getEnclosingType();
            JParameterizedType isParameterizedType = enclosingType
                    .isParameterized();
            if (isParameterizedType != null) {
                JMethod[] methods = isParameterizedType.getMethods();
                for (int i = 0; i < methods.length; ++i) {
                    if (methods[i] == syncMethod) {
                        /*
                         * Use the generic version of the method to ensure that
                         * the server can find the method using the erasure of
                         * the generic signature.
                         */
                        syncMethod = isParameterizedType.getBaseType()
                                .getMethods()[i];
                    }
                }
            }
            
            generateProxyMethod(w, serializableTypeOracle, syncMethod,
                    asyncMethod);
        }
    }
    
    private String getProxyQualifiedName() {
        String[] name = Shared.synthesizeTopLevelClassName(serviceIntf,
                PROXY_SUFFIX);
        return name[0].length() == 0 ? name[1] : name[0] + "." + name[1];
    }
    
    private String getProxySimpleName() {
        String[] name = Shared.synthesizeTopLevelClassName(serviceIntf,
                PROXY_SUFFIX);
        return name[1];
    }
    
    private String getRemoteServiceRelativePath(
            TreeLogger javadocAnnotationDeprecationBranch) {
        String[][] metaData = serviceIntf.getMetaData(ENTRY_POINT_TAG);
        if (metaData.length != 0) {
            if (javadocAnnotationDeprecationBranch != null) {
                javadocAnnotationDeprecationBranch.log(TreeLogger.WARN,
                        "Deprecated use of " + ENTRY_POINT_TAG
                                + "; Please use "
                                + RemoteServiceRelativePath.class.getName()
                                + " instead", null);
            }
            return metaData[0][0];
        } else {
            RemoteServiceRelativePath moduleRelativeURL = serviceIntf
                    .getAnnotation(RemoteServiceRelativePath.class);
            if (moduleRelativeURL != null) {
                return "\"" + moduleRelativeURL.value() + "\"";
            }
        }
        
        return null;
    }
    
    private ResponseReader getResponseReaderFor(JType returnType) {
        if (returnType.isPrimitive() != null) {
            return JPRIMITIVETYPE_TO_RESPONSEREADER.get(returnType
                    .isPrimitive());
        }
        
        if (returnType.getQualifiedSourceName().equals(
                String.class.getCanonicalName())) {
            return ResponseReader.STRING;
        }
        
        return ResponseReader.OBJECT;
    }
    
    private String writeSerializationPolicyFile(TreeLogger logger,
            GeneratorContext ctx, SerializableTypeOracle sto)
            throws UnableToCompleteException {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            OutputStreamWriter osw = new OutputStreamWriter(
                    baos,
                    SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING);
            PrintWriter pw = new PrintWriter(osw);
            
            JType[] serializableTypes = sto.getSerializableTypes();
            for (int i = 0; i < serializableTypes.length; ++i) {
                JType serializableType = serializableTypes[i];
                String binaryTypeName = sto
                        .getSerializedTypeName(serializableType);
                boolean maybeInstantiated = sto
                        .maybeInstantiated(serializableType);
                pw.print(binaryTypeName + ", "
                        + Boolean.toString(maybeInstantiated) + '\n');
            }
            
            // Closes the wrapped streams.
            pw.close();
            
            byte[] serializationPolicyFileContents = baos.toByteArray();
            String serializationPolicyName = Util
                    .computeStrongName(serializationPolicyFileContents);
            
            String serializationPolicyFileName = SerializationPolicyLoader
                    .getSerializationPolicyFileName(serializationPolicyName);
            OutputStream os = ctx.tryCreateResource(logger,
                    serializationPolicyFileName);
            if (os != null) {
                os.write(serializationPolicyFileContents);
                ctx.commitResource(logger, os);
            } else {
                logger.log(TreeLogger.TRACE,
                        "SerializationPolicy file for RemoteService '"
                                + serviceIntf.getQualifiedSourceName()
                                + "' already exists; no need to rewrite it.",
                        null);
            }
            
            return serializationPolicyName;
        } catch (UnsupportedEncodingException e) {
            logger
                    .log(
                            TreeLogger.ERROR,
                            SerializationPolicyLoader.SERIALIZATION_POLICY_FILE_ENCODING
                                    + " is not supported", e);
            throw new UnableToCompleteException();
        } catch (IOException e) {
            logger.log(TreeLogger.ERROR, null, e);
            throw new UnableToCompleteException();
        }
    }

}
